/*
 * \file common.c
 *
 *	\brief Definitions for declarations in common.h
 *
 *  Created on: Aug 30, 2012
 *      Author: Adrian Ortega Garcia
 *       email: elfus0.1@gmail.com
 *
 */

#include <stdlib.h>
#include "common.h"
#include "logging.h"
#include "elf_t.h"
static 	int	mem_allocated = 0;

void	print_binary_content(FILE * fp)
{
	long int size = 0;
	int offset = 0;
	fseek(fp,0, SEEK_END);
	size = ftell(fp);
	fseek(fp, 0, SEEK_SET);

	union dword_t{
		unsigned int dword;
		unsigned char byte[4];
	} un1, un2;

	LOG_INFO("Binary information:\n");
	LOG_INFO("Offset   |   dword    |   dword    |  ASCII Representation\n");
	LOG_INFO("=============================================================\n");
	while(!feof(fp)){
		// We want to print byte by byte because Intel processors are little endian.
		// Printing an int will give us the wrong byte order.
		LOG_INFO("%08x: ",offset);
		un1.dword = 0;
		offset += fread(&un1.dword, sizeof(unsigned char), 4, fp );
		LOG_INFO("%02x %02x %02x %02x  ", un1.byte[0], un1.byte[1], un1.byte[2], un1.byte[3]);
		un2.dword = 0;
		offset += fread(&un2.dword, sizeof(unsigned char), 4, fp );
		LOG_INFO("%02x %02x %02x %02x | ", un2.byte[0], un2.byte[1], un2.byte[2], un2.byte[3]);

		LOG_INFO("%c%c%c%c", is_valid_char(un1.byte[0]),
										  is_valid_char(un1.byte[1]),
										  is_valid_char(un1.byte[2]),
										  is_valid_char(un1.byte[3]));
		LOG_INFO("%c%c%c%c\n", is_valid_char(un2.byte[0]),
										  is_valid_char(un2.byte[1]),
										  is_valid_char(un2.byte[2]),
										  is_valid_char(un2.byte[3]));

	}
	LOG_INFO("=============================================================\n");
	LOG_INFO("File size: %ld (%#08x) bytes.\n", size, (unsigned int)size);
}

endianess_t check_endianess(void)
{
	int i = 1;
	char *p = (char*) &i;

	if( *p == 1){
		LOG_INFO("Your processor is LITTLE endian.\n");
		return LIL_ENDIAN;
	}else{
		LOG_INFO("Your processor is BIG endian.\n");
		return BG_ENDIAN;
	}
}

char	is_valid_char(char a)
{
	if(a >= 33 && a<= 126)
		return a;
	return '.';
}

void print_available_features(void)
{
	LOG_INFO("Available features:\n");
	LOG_INFO("=============================================================\n");
	LOG_INFO("Feature 1: Open binary files and show the binary content.\n");
	LOG_INFO("Feature 2: Identify ELF headers and print content.\n");
	LOG_INFO("=============================================================\n");
}

void	list_add_item(item *first_item, item *new_ptr)
{
	if(!(first_item->ptr)){
		/* Empty list */
		first_item->ptr = new_ptr;
		new_ptr->next = NULL;
	} else {
		/* Add register field to the end of the list */
		item *list_p;
		for(list_p = first_item->ptr; list_p != NULL; list_p = list_p->next){
			item *current = list_p;
			if( current->next == NULL ){
				current->next = new_ptr;
				new_ptr->next = NULL;
				break;
			}
		}
	}
	mem_allocated += sizeof(item);
}


void	list_free(item *first_item)
{
	item * current = first_item;
	while(current != NULL){
		item *next = current->next;
		free((void*)current);
		mem_allocated -= sizeof(item);
		current = next;
	}
	LOG_DEBUG("Remaining bytes allocated: %d", mem_allocated);
	first_item = 0;
}

FILE *		open_file(const char *name)
{
	FILE *fd = fopen(name, "rb");
	if(fd == NULL)
		return NULL;

	fseek(fd,0,SEEK_END);
	size_t pos = ftell(fd);
	if(pos == -1){
		LOG_ERR("Error getting file size.\n");
		fclose(fd);
		return NULL;
	}
	LOG_INFO("File size %ld (%#08x) bytes.\n",pos, (unsigned int)pos);
	if( pos < MIN_FILE_SIZE || pos > MAX_FILE_SIZE){
		LOG_ERR("Wrong file size, should be between %d (%#08x) "
				"and %d (%#08x) bytes.\n",\
				MIN_FILE_SIZE, MIN_FILE_SIZE, MAX_FILE_SIZE, MAX_FILE_SIZE);
		fclose(fd);
		return NULL;
	}
	rewind(fd);
	return fd;
}


char *	machine_type(int type)
{
	switch(type)
	{
	case EM_NONE: return STR(EM_NONE);
	case EM_M32: return STR(EM_M32);
	case EM_SPARC: return STR(EM_SPARC);
	case EM_386: return STR(EM_386);
	case EM_68K: return STR(EM_68K);
	case EM_88K: return STR(EM_88K);
	case EM_860: return STR(EM_860);
	case EM_MIPS: return STR(EM_MIPS);
	case EM_X86_64: return "AMD x86-64 architecture "STR(EM_X86_64);
	//\todo Add the rest of machine types.
	default: return "Unknown machine type";
	}
	return "n/a";
}

char *	elf_type(int type)
{
	switch(type)
	{
	case ET_NONE: return "Invalid ELF type ("STR(ET_NONE)")";
	case ET_REL: return "Relocatable ELF File ("STR(ET_REL)")";
	case ET_EXEC: return "Executable ELF File ("STR(ET_EXEC)")";
	case ET_DYN: return "Shared object ELF File ("STR(ET_DYN)")";
	case ET_CORE: return "Core ("STR(ET_CORE)")";
	case ET_LOPROC: return STR(ET_LOPROC);
	case ET_HIPROC: return STR(ET_HIPROC);
	default: return "Unknown ELF Type";
	}
	return "n/a";
}

char *	elf_os_abi(int type)
{
	switch(type)
	{
	case ELFOSABI_SYSV: return "UNIX System V ABI ("STR(ELFOSABI_SYSV)")";
	case ELFOSABI_HPUX: return "HP-UX ("STR()")";
	case ELFOSABI_NETBSD: return "NetBSD ("STR()")";
	case ELFOSABI_GNU: return "Object uses GNU ELF extensions";
	case ELFOSABI_SOLARIS: return "Sun Solaris. ("STR()")";
	case ELFOSABI_AIX: return "IBM AIX ("STR()")";
	case ELFOSABI_IRIX: return "SGI Irix ("STR()")";
	case ELFOSABI_FREEBSD: return "FreeBSD ("STR()")";
	case ELFOSABI_TRU64: return "Compaq TRU64 UNIX ("STR()")";
	case ELFOSABI_MODESTO: return "Novell Modesto ("STR()")";
	case ELFOSABI_OPENBSD: return "OpenBSD ("STR()")";
	case ELFOSABI_ARM_AEABI: return "ARM EABI ("STR()")";
	case ELFOSABI_ARM: return "ARM ("STR()")";
	case ELFOSABI_STANDALONE: return STR(ELFOSABI_STANDALONE);
	default: return "Unkown OS/ABI";
	}
	return "n/a";
}

char *	section_type(int type)
{
	switch(type)
	{
	case SHT_NULL		: return "NULL";
	case SHT_PROGBITS	: return "PROGBITS";
	case SHT_SYMTAB		: return "SYMTAB";
	case SHT_STRTAB		: return "STRTAB";
	case SHT_RELA		: return "RELA";
	case SHT_HASH		: return "HASH";
	case SHT_DYNAMIC	: return "DYNAMIC";
	case SHT_NOTE		: return "NOTE";
	case SHT_NOBITS		: return "NOBITS";
	case SHT_REL		: return "REL";
	case SHT_SHLIB		: return "SHLIB";
	case SHT_DYNSYM		: return "DYNSIM";
	case SHT_INIT_ARRAY	: return "INIT_ARRAY";
	case SHT_FINI_ARRAY	: return "FINI_ARRAY";
	case SHT_PREINIT_ARRAY: return "PREINIT_ARRAY";
	case SHT_GROUP: return "SHT_GROUP";
	case SHT_SYMTAB_SHNDX: return "SYMTAB_SHNDX";
	case SHT_NUM: return "NUM";
	case SHT_LOOS		: return "LOOS";
	case SHT_GNU_ATTRIBUTES: return "GNU_ATTRIBUTES";
	case SHT_GNU_HASH: return "GNU_HASH";
	case SHT_GNU_LIBLIST: return "GNU_LIBLIST";
	case SHT_CHECKSUM: return "CHECKSUM";
	case SHT_LOSUNW: return "LOSUNW";

	case SHT_SUNW_COMDAT: return "SUNW_COMDATeturn";
	case SHT_SUNW_syminfo: return "SUNW_syminfo";
	case SHT_GNU_verdef: return "GNU_verdefeturn";
	case SHT_GNU_verneed: return "GNU_verneed";

	case SHT_HIOS		: return "HIOS";
	case SHT_LOPROC		: return "LOPROC";
	case SHT_HIPROC		: return "HIPROC";
	case SHT_LOUSER		: return "LOUSER";
	case SHT_HIUSER		: return "HIUSER";
	}
	return "n/a";
}

char *	phdr_type(Elf64_Word type)
{
	switch(type){
	case PT_NULL: return "NULL";
	case PT_LOAD: return "LOAD";
	case PT_DYNAMIC: return "DYNAMIC";
	case PT_INTERP: return "INTERP";
	case PT_NOTE: return "NOTE";
	case PT_SHLIB: return "SHLIB";
	case PT_PHDR: return "PHDR";
	case PT_TLS : return "TLS";
	case PT_NUM : return "NUM";
	case PT_LOOS: return "LOOS";
	case PT_GNU_EH_FRAME : return "EH_FRAME";
	case PT_GNU_STACK : return "STACK";
	case PT_GNU_RELRO : return "RELRO";
	case PT_SUNWBSS : return "SUNWBSS";
	case PT_SUNWSTACK : return "SUNWSTACK";
	case PT_HIOS: return "HIOS";
	case PT_LOPROC: return "LOPROC";
	case PT_HIPROC: return "HIPROC";
	default:
		if(type > PT_LOOS && type < PT_HIOS){
			return "OS";
		}
		if(type > PT_LOPROC && type < PT_HIPROC ){
			return "PROC";
		}
		LOG_DEBUG("TYPE: %d (%#016x)\n", type,type);
		return "UNKNOWN";
	}
}

char *	phdr_flag_type(Elf64_Word type)
{
	switch(type)
	{
	case PF_X: return "--x";
	case PF_W: return "-w-";
	case PF_R: return "r--";
	case (PF_X | PF_W): return "-wx";
	case (PF_X | PF_R): return "r-x";
	case (PF_W | PF_R): return "rw-";
	case PF_MASKOS: return "reserved";
	case PF_MASKPROC: return "reserved";
	default : return "UNKOWN FLAG";

	}
}

